package middleware;

/**
 * Multicast project Bidois Morgan & Thomas Nunes
 *
 */
import Engine.Message;
import Engine.TypeMessage;
import static Engine.TypeMessage.*;
import Test.ExecuteServer;
import Test.I_RecvMsgHandler;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * The server multicast layer
 *
 * @author Thomas
 */
public class ServerMulticast extends AbstractMulticast implements I_ServerMulticast, I_RecvMulticastHandler {

    private int countClient = 0;
    private int portForClient = 1500;
    private final int nbGroupClientTotal = 2;
    private int nbGroupClientCurrent = 0;
    private int nbGroupClientServerSocketReady = 0; //to know the number of client who create their own serveurSocket
    private I_RecvMsgHandler exServeur;
    private int clientHistoric;
    private ArrayList<String> historics = new ArrayList<>();

    public ServerMulticast(I_RecvMsgHandler executeServeur) {
        try {
            InetAddress addr = InetAddress.getByName("localhost");
            this.InitializeAsServer(addr, 9090, executeServeur, this);
            this.exServeur = executeServeur;
        } catch (IOException ex) {
            Logger.getLogger(ServerMulticast.class.getName()).log(Level.SEVERE, "Constructor server fail!", ex);
        }
    }

    /**
     * Send the directory to clients the message : Flag DIR - Adr - Port -
     * Status - Adr
     */
    @Override
    public void sendDirectory() {
        String msgDir = "" + /*countClient +*/ directory.getDirectoryToSend();
        this.send2All(msgDir.getBytes(), TypeMessage.SEND_DIRECTORY);
    }

    @Override
    public void addClientToDirectory(int idClient, String name) {
        ClientInfos client = null;
        try {
            client = new ClientInfos(idClient, name, InetAddress.getByName("localhost"), portForClient);
        } catch (UnknownHostException ex) {
            Logger.getLogger(ServerMulticast.class.getName()).log(Level.SEVERE, "cant create client info", ex);
        }
        directory.addClient(client);

        this.setNbGroupClientCurrent(countClient++);
        portForClient++;

//        System.out.println(directory.toString());
    }

    @Override
    public void printMsg(byte[] msg) {
        System.out.println("Le server a recu : " + new String(msg));
    }

    /**
     * send a simple message to the client
     *
     * @param data the message
     */
    @Override
    public void send(byte[] data) {
        this.logicalClock++;
        engine.send(data, TypeMessage.NORMAL, idClient, this.logicalClock);

    }

    @Override
    public void receivedCB(Message message, SocketChannel socket) {
        int lcSend = message.getLogicalClock();
        if (lcSend > this.logicalClock) {
            this.logicalClock = lcSend;
        }
        switch (message.getHeader().getType()) {
            case NORMAL:
                break;
            case GIVE_ME_A_MESSAGE:
                break;
            case NEW_CONNECTION:
                break;
            case SOMEONE_LEAVE:
                break;
            case START_BURST:
                break;
            case STOP_BURST:
                break;
            case ACK:
                break;
            case SEND_ID:
                String strId = new String(message.getContent());
                String[] idNom = strId.split(":");
                int id = Integer.parseInt(idNom[0]);
                String nom = idNom[1];
                if (directory.isNameExist(nom)) {
                    //this name already exist, we refused
                    nom = nom + message.getIdClient();
                    this.engine.send(socket, "nom".getBytes(), SEND_ID, idClient, logicalClock);
                }
                //A client send to the server its ID
                this.addClientToDirectory(id, nom);
                this.nbGroupClientCurrent++;
                if (nbGroupClientCurrent == nbGroupClientTotal) {
                    //If all client are here, we send the directory to the group
                    this.sendDirectory();

                } else if (nbGroupClientCurrent > nbGroupClientTotal) {
                    this.sendDirectoryToClient(socket);
                }
                break;
            case CLIENT_SERVERSOCKET_READY:
                //Server receive information 
                nbGroupClientServerSocketReady++;
                if (nbGroupClientServerSocketReady == nbGroupClientTotal) {
                    // id at -1 cause server 
                    this.send2All("Start".getBytes(), TypeMessage.BEGIN_CONNECTION);
                } else if (nbGroupClientServerSocketReady >= nbGroupClientTotal) {
                    this.engine.send(socket, "Start".getBytes(), TypeMessage.BEGIN_CONNECTION, 0, 0);
                }
                break;
            case TAKE_MY_HISTORIC:
                String strHist = new String(message.getContent());
                analyseHistoric(strHist);
                break;
        }
    }

    /**
     * kill a random client
     */
    public void killSomeone() {
        try {
            Random r = new Random();
            if (this.clientList.size() > 0) {
                int indice = Math.abs(r.nextInt()) % this.clientList.size();
                SocketChannel socket2kill = this.clientList.get(indice);
                this.engine.send(socket2kill, "".getBytes(), DIE, idClient, logicalClock);
                this.clientList.remove(indice);
            }
        } catch (IndexOutOfBoundsException e) {
        }
    }

    /**
     * Ask to all client to send their log history to the server for analyse
     */
    public void askForHistoric() {
        clientHistoric = 0;
        historics = new ArrayList<>();
        this.send2All("GiveMeYourHistoric".getBytes(), GIVE_ME_A_MESSAGE);
    }

    /**
     * Analyse historic of all client to knwo if they are ordered
     *
     * @param histo the history
     */
    public void analyseHistoric(String histo) {
        if (clientHistoric < clientList.size()) {
            historics.add(histo);
            clientHistoric++;
            if (clientHistoric == clientList.size()) {
                exServeur.updateOrdered(compareHistoric(histo), this.logicalClock);
            }
        }
    }

    /**
     * Compare a log to all others log historics between client to test if its
     * ordered.
     *
     * @param histToCompare the log to compare
     * @return if the client are ordered.
     */
    public boolean compareHistoric(String histToCompare) {
        boolean areSame = true;
        for (String hist1 : historics) {
            if (hist1.length() == histToCompare.length()) {
                if (!hist1.equals(histToCompare)) {
                    return false;
                }
            } else {
                if (hist1.length() < histToCompare.length()){
                    return histToCompare.contains(hist1);
                } else {
                    return hist1.contains(histToCompare);
                }
            }

        }
        return areSame;
    }

    public int getNbGroupClientTotal() {
        return nbGroupClientTotal;
    }

    private void sendDirectoryToClient(SocketChannel socketChannel) {
        String msgDir = "" + /*countClient +*/ directory.getDirectoryToSend();
        this.engine.send(socketChannel, msgDir.getBytes(), TypeMessage.SEND_DIRECTORY, 0, 0);
    }

    public void setNbGroupClientCurrent(int countClient) {
        this.nbGroupClientCurrent = countClient;
        this.handler.nbClientCurrentCB(this.clientList.size());
    }

    public void stopBurst() {
        if (burstmode) {
            this.send2All("".getBytes(), TypeMessage.STOP_BURST);
            burstmode = false;
        }

    }

    public void startBurst() {
        if (!burstmode) {
            this.send2All("".getBytes(), TypeMessage.START_BURST);
            burstmode = true;
        }
    }
}
